home *** CD-ROM | disk | FTP | other *** search
/ NetNews Offline 2 / NetNews Offline Volume 2.iso / news / comp / lang / c-part2 / 13888 < prev    next >
Encoding:
Text File  |  1996-08-05  |  6.3 KB  |  157 lines

  1. Newsgroups: comp.lang.c
  2. Path: new-news.sprintlink.net!eskimo!scs
  3. From: scs@eskimo.com (Steve Summit)
  4. Subject: Re: How to use assert( )
  5. X-Nntp-Posting-Host: eskimo.com
  6. Message-ID: <DpnqFE.E8v@eskimo.com>
  7. Sender: news@eskimo.com (News User Id)
  8. Organization: schmorganization
  9. References: <4kc3k7$dur@orion.cybercom.net>
  10. Date: Wed, 10 Apr 1996 17:40:26 GMT
  11.  
  12. In article <4kc3k7$dur@orion.cybercom.net>, nield@cybercom.net writes:
  13. > I'm just starting my first project big enough to split among many
  14. > people, and from the vague explanations I've heard, assert is supposed
  15. > to be a usefull way to cause errors when someone passes your code bad
  16. > values.
  17.  
  18. Indeed (as long as we understand that "someone" is one of your
  19. fellow programmers, and *not* one of the program's users).
  20.  
  21. An assertion (which is what C's assert() macro implements)
  22. allows you to document the assumptions made by a piece of code,
  23. and furthermore to arrange that the code test its assumptions
  24. as it runs.  Usually, these assumptions boil down to "the rest
  25. of the program is written correctly," and in a large program,
  26. particularly one worked on by many people, this is of course
  27. *not* always a valid assumption, and so is eminently worth
  28. testing.
  29.  
  30. Using the assert() macro is simple (as it should be, if
  31. assertions are to be conveniently used): after including
  32. its header:
  33.  
  34.     #include <assert.h>
  35.  
  36. you sprinkle the code with "calls" to
  37.  
  38.     assert(expr)
  39.  
  40. where expr is an expression that "should be" true, that is, that
  41. evaluates to nonzero if the particular assumption is indeed met.
  42.  
  43. Judicious assertions are unquestionably a Good Thing, but I
  44. suppose it's possible to go overboard with them, particularly
  45. when you've just discovered them.  [The verb "sprinkle" above is
  46. perhaps injudicious.]  There's no point in a single module's
  47. checking the sanity of the entire world; it need only check the
  48. particular conditions upon which it depends.  Typically, the
  49. conditions checked by an assertion are those which, if false,
  50. would cause later statements in the module containing the
  51. assertion to fail (by crashing, damaging data structures,
  52. generating incorrect output, etc.).  The assertion test is
  53. clearly preferable (even though what a failed assertion typically
  54. does is deliberately crash your program) because it documents
  55. what's going on reasonably clearly, while the later crash (if the
  56. assertion didn't catch the problem first) might be much more
  57. mysterious.
  58.  
  59. Rudimentary examples would be
  60.  
  61.     assert(n != 0);
  62.     average = sum / n;
  63.  
  64. in a function which computes an average and is documented as only
  65. working on non-empty arrays, and
  66.  
  67.     assert(p != NULL);
  68.  
  69. in a function which does a lot of work with a pointer p.
  70. As a more detailed example, just yesterday I found myself
  71. writing code which ended up looking something like this:
  72.  
  73.     if(list->next == NULL)
  74.         {
  75.         /* several lines special-casing a 1-element list */
  76.         return;
  77.         }
  78.  
  79.     /* several lines to preprocess list->item */
  80.  
  81.     assert(list->next != NULL);
  82.     for(lp = list->next; lp != NULL; lp = list->next)
  83.         process(lp->item);
  84.  
  85. In this code, a list with exactly one element is treated
  86. completely differently, and a list with more than one element has
  87. its first element treated specially ("preprocessed") before the
  88. remainder of the list is processed.  When I first found myself
  89. writing
  90.  
  91.     for(lp = list->next; lp != NULL; lp = list->next)
  92.  
  93. I said, "Wait a minute.  That's a weird loop.  I almost never
  94. write loops like that.  What If list->next starts out null?"
  95. Furthermore, it happened that the code would *not* have behaved
  96. correctly if none of the "process" steps were taken.
  97.  
  98. Looking farther up the page, we see that it "can't happen" that
  99. list->next is ever null by the time the loop in question is
  100. reached, but we have to look just enough farther up, and the
  101. intervening preprocessing code is complicated enough, and in
  102. fact in the actual code the expression which I simplify here as
  103. (list->next == NULL) is complicated enough, that we're not being
  104. unduly paranoid if we contemplate the possibility that, sometime
  105. down the road, a modification of some kind could vertently or
  106. inadvertently cause the assumption to be violated.  Thus the call
  107. to assert() just before the loop.
  108.  
  109. (If you're curious, the code in question extracts revisions from
  110. RCS files by constructing a list of delta texts to apply.  The
  111. first "delta text" is the head revision in its entirety, which is
  112. the file we want if that's the revision we're extracting, and is
  113. the basis to apply the remaining deltas to otherwise.)
  114.  
  115. It's important to note that assertions should only be used to
  116. test for programming errors.  You should never use an assertion
  117. to test that a function like fopen() or malloc() has been called
  118. successfully, or that a user has supplied correct input.
  119. Real-world errors (files not being openable, sufficient memory
  120. not being available, users stubbornly refusing to provide
  121. syntactically correct input) are properly handled with explicit
  122. code which detects the error, issues its own detailed message,
  123. and cleans up and continues if appropriate.  In other words,
  124. assertions are properly used to detect unexpected failures caused
  125. by programming errors, *not* to detect real-world failures, which
  126. in a properly-written program should not be unexpected.
  127.  
  128. Once a program is "working," some programmers take that as a sign
  129. that it contains no more bugs, and worry that testing the
  130. assertions wastes too much CPU time, or that they might one day
  131. be embarrassed to have a customer ask, "What does `assertion
  132. failed in line 5341 of temporary_kludges.c' mean, and how happy
  133. should I be to have spent $2000 on a program with at least 5341
  134. lines of temporary kludges?"  These programmers can assuage their
  135. fears by defining the macro NDEBUG, which causes the remaining
  136. calls to assert() in their code to be replaced with code which
  137. does nothing (neither using any text space or CPU time, nor of
  138. course validating any assumptions, either).
  139.  
  140. One last point: last I heard, the language lawyers over in
  141. comp.std.c had determined that the operand of the assert()
  142. macro is *not* quite the same as, say, the controlling Boolean
  143. expression of an if statement, such that the call
  144.  
  145.     assert(p);
  146.  
  147. where p is a pointer, might not be strictly conforming.
  148. It's therefore said to be safer to call
  149.  
  150.     assert(p != NULL);
  151.  
  152. (or, if you prefer, assert(p != 0)), which happens to be the sort
  153. of thing I prefer writing anyway.
  154.  
  155.                     Steve Summit
  156.                     scs@eskimo.com
  157.